/*
Copyright (C) 2015  Marien Raat

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <diamondsquare.h>

#include <math.h>

HeightmapGenerator::HeightmapGenerator(std::mt19937 *mt)
{
    this->mt = mt;
    stdDist = new std::normal_distribution<double>(0.0, 1.0);

    mapInitialized = false;
    firstMap = true;
}

// The map itself is not deleted with the HeightmapGenerator
// because it might still be needed. The user of the class
// is responsible for deleting the map when it is no longer
// needed.
HeightmapGenerator::~HeightmapGenerator()
{
    delete stdDist;
}

void HeightmapGenerator::createNewMap(DiamondSquareMapSettings settings)
{
    currentMapSettings = settings;
    if (firstMap || !settings.overwriteOldMap)
	map = new double[settings.size * settings.size];
    mapInitialized = true;
    firstMap = false;

    for (int i = 0; i < settings.size * settings.size; i++)
	map[i] = 0.0;

    currentPassSettings.map = map;
    currentPassSettings.passSize = settings.size - 1;
    currentPassSettings.displacementModifier = 1.0;
    currentPassSettings.mapSize = settings.size;

    // Set up different presets
    if (settings.mapType == MAPTYPE_ISLAND) {
	fillSidesWith(map, -7.0, settings.size);
	currentPassSettings.passSize /= 2;
    }
    if (settings.mapType == MAPTYPE_ARCHIPELAGO) {
	fillSidesWith(map, -1.0, settings.size);
	currentPassSettings.passSize /= 8;
    }
}

void HeightmapGenerator::pass()
{
    if (!isMapDone()) {
	passDiamondSquare(&currentPassSettings);
	currentPassSettings.passSize /= 2;
	currentPassSettings.displacementModifier
            = currentMapSettings.modifierFunction
            (log((double) currentPassSettings.passSize
                 / (double) currentPassSettings.mapSize)
             / log(0.5),
             currentMapSettings.h);
    }
}

void HeightmapGenerator::finishMap() {
    while (!isMapDone()) pass();
}

bool HeightmapGenerator::isMapDone()
{
    return currentPassSettings.passSize <= 1;
}

void HeightmapGenerator::passDiamondSquare(PassSettings *settings)
{
    // Pass diamond
    for (int x = settings->passSize / 2; x < settings->mapSize;
	 x += settings->passSize) {
	for (int y = settings->passSize / 2; y < settings->mapSize;
	     y += settings->passSize) {
	    diamond(x, y, settings);
	}
    }
    // Pass square
    for (int x = 0; x < settings->mapSize;
	 x += settings->passSize / 2) {
	for (int y = settings->passSize / 2; y < settings->mapSize;
	     y += settings->passSize) {
	    square(x, y, settings);
	}
	x += settings->passSize / 2;
	if (x < settings->mapSize) {
	    for (int y = 0; y < settings->mapSize;
		 y += settings->passSize) {
		square(x, y, settings);
	    }
	}
    }
}

void HeightmapGenerator::diamond(int x, int y, PassSettings *settings)
{
    int points = 0,
	dif = settings->passSize / 2;
    double total = 0.0;
    // Add the values of the square around (x, y) to total

    // Simply cycles through the positions [(x - dif, y + dif),
    // (x + dif, y + dif), (x - dif, y - dif), (x + dif, y + dif)]
    for (int i = 0; i < 4; i++) {
	int positionX = x + (i % 2 == 0 ? - dif : + dif),
	    positionY = y + (i / 2 > 0 ? - dif : + dif);
	if (isValid(positionX, positionY, settings->mapSize)) {
	    points++;
	    total += settings->map
		[TO_ARRAY_INDEX(positionX, positionY, settings->mapSize)];
	}
    }

    total /= (double) points;
    total += drand() * settings->displacementModifier;
    settings->map[TO_ARRAY_INDEX(x, y, settings->mapSize)] = total;
}

void HeightmapGenerator::square(int x, int y, PassSettings *settings)
{
    int points = 0;
    double total = 0.0;
    int dif = settings->passSize / 2;
    // Add the values in a diamond around (x, y) to the total

    // Simply cycles through the positions [(x + dif, y),
    // (x, y + dif), (x - dif, y), (x, y - dif)]
    for (int i = 0; i < 4; i++) {
	int positionX = x + (i % 2 == 0 ?
			     (i / 2 > 0 ? - dif : + dif) : 0),
	    positionY = y + (i % 2 == 0 ? 0 :
			     (i / 2 > 0 ? - dif : + dif));
	if (isValid(positionX, positionY, settings->mapSize)) {
	    points++;
	    total += settings->map
		[TO_ARRAY_INDEX(positionX, positionY, settings->mapSize)];
	}
    }

    total /= (double) points;
    total += drand() * settings->displacementModifier;
    settings->map[y * settings->mapSize + x] = total;
}

bool HeightmapGenerator::isValid(int x, int y, int size)
{
    return x >= 0 && x < size && y >= 0 && y < size;
}

double HeightmapGenerator::drand()
{
    return (*stdDist)(*mt);
}

void HeightmapGenerator::fillSidesWith(double *map,
				       double cornerValue,
				       int size)
{
    for (int x = 0; x < size; x++) {
	map[x] = cornerValue;
	map[x + size * (size - 1)] = cornerValue;
    }
    for (int y = 0; y < size; y++) {
	map[y * size] = cornerValue;
	map[y * size + size - 1] = cornerValue;
    }
}

double logaritmicDisplacementModifier(int pass, double h)
{
    return pow(2.0, -1.0 * h * pass);
}

double customDisplacementModifier(int pass, double h)
{
    if (pass < 8) return pow(2.0, -1.0 * h * pass);
    switch (pass) {
    case 8:
        return pow(2.0, -1.0 * h * pass * 1.1);
    case 9:
        return pow(2.0, -1.0 * h * pass * 1.4);
    case 10:
        return pow(2.0, -1.0 * h * pass * 2.0);
    default:
        return pow(2.0, -1.0 * h * pass * 2.5);
        break;
    }
}
